package com.zzuli.yxy.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.code.kaptcha.Producer;
import com.zzuli.yxy.ListStringConverter;
import com.zzuli.yxy.entity.Employee;
import com.zzuli.yxy.entity.LoginUser;
import com.zzuli.yxy.entity.Menu;
import com.zzuli.yxy.enumeration.AppHttpCodeEnum;
import com.zzuli.yxy.result.R;
import com.zzuli.yxy.util.IpUtils;
import com.zzuli.yxy.util.JwtUtils;
import com.zzuli.yxy.util.TXCloudFileUtils;
import com.zzuli.yxy.mapper.EmployeeMapper;
import com.zzuli.yxy.mapper.MenuMapper;
import com.zzuli.yxy.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import com.zzuli.yxy.service.EmployeeService;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * @auther pony
 * @create 2023-03-05 14:44
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private Producer captchaProducer;

    @Autowired
    private EmployeeMapper employeeMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RoleMapper roleMapper;

    //腾讯云对象存储 路径-访问域名
    private static String url = "https://pony-1312090731.cos.ap-beijing.myqcloud.com";

    // 头像存储的文件夹
    private static String imgPath = "yxy/images/";

    //默认头像地址
    private static String defaultImgUrl = "https://pony-1312090731.cos.ap-beijing.myqcloud.com/yxy/images/default.jpg";

    /**
     * 获取验证码方法，以流的形式写回前端
     * @param request
     * @param response
     */
    @Override
    public void getVerifyCode(HttpServletRequest request, HttpServletResponse response) {
        //1、生成验证码的数字
        String verifyCode = captchaProducer.createText();
        //2、将验证码放到 redis 缓存里（设置过期时间120s）
        redisTemplate.opsForValue().set(getCaptchaKey(request), verifyCode, 120, TimeUnit.SECONDS);
        //3、生成验证码图片
        BufferedImage verifyCodeImage = captchaProducer.createImage(verifyCode);
        //4、将图片以流的形式写出去
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            ImageIO.write(verifyCodeImage, "png", outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 验证码 存入redis缓存的 key 值
     * 通过 用户IP + 用户信息 加密后作为 key 值
     * @param request
     * @return
     */
    public String getCaptchaKey(HttpServletRequest request){
        //1、调用工具方法获取 ip
        String ip = IpUtils.getIpAddr(request);
        /*2、获取用户信息，User-Agent，网站服务器通过识别 “UA”来确定用户所使用
         的操作系统版本、CPU 类型、浏览器版本等信息。*/
        String userInfo = request.getHeader("User-Agent");
        //3、将 ip 和 用户信息 userInfo 加密作为 key
        String captchaKey = DigestUtils.md5DigestAsHex((ip+userInfo).getBytes());
        //4、设置前缀并返回
        return captchaKey;
    }

    /**
     * 登录
     * @param employee
     * @param verifyCode
     * @return
     */
    @Override
    public R login(Employee employee, String verifyCode, HttpServletRequest request) {
        //1、判断验证码是否正确
        //获取 redis 中验证码的 key 值
        String captchaKey = getCaptchaKey(request);
        //根据 key 值取 验证码
        Object value = redisTemplate.opsForValue().get(captchaKey);
        if (value==null){
            return R.error(AppHttpCodeEnum.ERROR).message("验证码过期");
        }
        if (!verifyCode.equals(value)){
            return R.error(AppHttpCodeEnum.ERROR).message("验证码错误");
        }
        //2、AuthenticationManager authenticationManager 进行用户认证
        /*认证方法需要传入一个 authentication 类，但他是一个接口，需要我们自己写一个实现类
        将用户名和密码封装到 authentication 类中
        官方提供了一些实现类，我们要使用 UsernamePasswordAuthenticationToken 实现类*/
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(employee.getUsername(), employee.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //3、如果认证没通过，给出对应的提示
        if (Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        //4、如果认证通过，使用userId生成一个jwt，jwt存入ResponseResult返回
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getEmployee().getId().toString();
        String jwtToken = JwtUtils.getJwtToken(userId);
        redisTemplate.opsForValue().set("login:"+userId, loginUser);
        return R.success(AppHttpCodeEnum.SUCCESS).message("登录成功").data("token", jwtToken);
    }

    /**
     * 登出
     * @return
     */
    @Override
    public R logout() {
        //1、获取SecurityContextHolder 中的用户 id
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authenticationToken.getPrincipal();
        Integer id = loginUser.getEmployee().getId();
        //2、删除 redis 中的值
        redisTemplate.delete("login:" + id);
        return  R.success(AppHttpCodeEnum.SUCCESS).message("退出成功！");
    }

    /**
     * 根据 token 获取员工信息    关灯
     * @param request
     * @return
     */
    @Override
    public R getEmployeeInfoByToken(HttpServletRequest request) {
        //1、解析 token，获取员工id
        String id = String.valueOf(JwtUtils.getUserIdByJwtToken(request));
        //2、根据 id，查询数据库，获取员工信息
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getId, id);
        Employee employee = employeeMapper.selectOne(queryWrapper);
        //3、用户信息封装装到统一的结果返回集，返回给前端
        return R.success(AppHttpCodeEnum.SUCCESS).message("根据 token 获取员工信息，成功！").data("employeeInfo", employee);
    }

    /**
     * 获取菜单列表信息
     * @param request
     * @return
     */
    @Override
    public R getMenuList(HttpServletRequest request) {
        //1、解析token，获取员工id
        Integer id = Integer.valueOf(String.valueOf(JwtUtils.getUserIdByJwtToken(request)));
        //2、根据员工id查询员工权限菜单list
        List<Menu> menusList = menuMapper.selectMenuListByEmployeeId(id);
        //3、将 menuList 转换为树形结构
        List<Menu> menuTreeList = getMenuTree(menusList);
        return R.success(AppHttpCodeEnum.SUCCESS).data("menuList", menuTreeList);
    }

    /**
     * 将 menuList 转化为树形结构
     * @param menusList 员工权限列表
     * @return
     */
    private List<Menu> getMenuTree(List<Menu> menusList) {
        //1、new 一个 List 集合，存放数据
        ArrayList<Menu> resultMenuList = new ArrayList<>();//权限列表

        for (Menu menu: menusList) {
            //2、继续遍历，寻找子节点
            for (Menu e: menusList) {
                if (e.getParentId().equals(menu.getId())){
                    menu.getChildren().add(e);
                }
            }
            //3、如果 ParentId = 0，表示此权限就是顶级父级菜单
            if (menu.getParentId()==0){
                resultMenuList.add(menu);
            }
        }
        return resultMenuList;
    }

    /**
     * 上传头像
     * @param file
     * @return
     */
    @Override
    public R uploadImage(MultipartFile file, HttpServletRequest request) {
        //1、判断图片是否过大
        if (file.getSize() > 30*1024*1024){
            return R.error(AppHttpCodeEnum.ERROR).message("上传的图片过大");
        }
        //2、解析 token，获取员工id
        String id = String.valueOf(JwtUtils.getUserIdByJwtToken(request));
        //3、判断文件是不是 图片类型
        //文件类型 图片类型是 image/jpg image/png
        String fileType = file.getContentType();
        //判断是否是以 image 开头
        if (!fileType.startsWith("image")){
            return R.error(AppHttpCodeEnum.ERROR).message("上传的文件不是图片类型");
        }
        //4、上传之前将用户之前的头像从云端存储桶中删除
        deleteImage(request);

        //5、上传图片
        // 上传文件 返回值是 key，即图片名
        String imgName = TXCloudFileUtils.upLoad(imgPath, file);
        //6、拼接 img 访问路径，并写入数据库
        String imgUrl = url + "/" + imgPath + imgName;
        LambdaUpdateWrapper<Employee> uw = new LambdaUpdateWrapper<>();
        uw.eq(Employee::getId, id);
        uw.set(Employee::getImage, imgUrl);
        int result = employeeMapper.update(null, uw);
        if (result!=1){
            return R.error(AppHttpCodeEnum.ERROR).message("头像上传失败，请联系管理员");
        }
        return R.success(AppHttpCodeEnum.SUCCESS).message("头像上传成功");
    }

    /**
     * 删除用户云端存储桶中的头像
     * 不能删除默认头像
     */
    public void deleteImage(HttpServletRequest request){
        //1、解析 token，获取员工id
        String id = String.valueOf(JwtUtils.getUserIdByJwtToken(request));
        //2、查询数据库，获取云端存储桶的图片路径
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.eq(Employee::getId, id);
        Employee employee = employeeMapper.selectOne(qw);
        String imageUrl = employee.getImage();
        if (!("".equals(imageUrl))&&!(url+"/"+imgPath+"default.jpg").equals(imageUrl)){
            /*3、字符串截取
             https://pony-1312090731.cos.ap-beijing.myqcloud.com/yxy/images/20230325040951670aad2931ff40129b1172fab07e9b28.jpg
             System.out.println("deleteImage: " + imageUrl);
             我们需要的字段为 yxy/images/20230325040951670aad2931ff40129b1172fab07e9b28.jpg
             https://pony-1312090731.cos.ap-beijing.myqcloud.com/ 长度为 52
             substring(int start) 从索引下标 为start 出开始截取，一直到最后 左闭右开
             52 即 yxy/im... "y" 的下表位置*/
            String key = imageUrl.substring((url+"/").length());
            TXCloudFileUtils.del(key);
        }
    }

    /**
     * 获取员工角色名称
     * @param request
     * @return
     */
    @Override
    public R getRoleNameByEmployeeId(HttpServletRequest request) {
        //1、解析 token 获取登录员工 id
        Integer id = Integer.valueOf(String.valueOf(JwtUtils.getUserIdByJwtToken(request)));
        return R.success(AppHttpCodeEnum.SUCCESS).
                message("员工角色名称").
                data("roleName", employeeMapper.selectByEmployeeId(id));
    }

    /**
     * 修改员工信息
     * @param employee
     * @return
     */
    @Override
    public R updateEmployee(Employee employee) {
        //1、判断员工账号是否重复，根据账号查是否能查出来自己以外的
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.eq(Employee::getUsername, employee.getUsername());
        qw.last("limit 1");
        Employee selectResult = employeeMapper.selectOne(qw);
        // 排除自己
        if (!Objects.isNull(selectResult)&&selectResult.getId()!=employee.getId()){
            return R.error(AppHttpCodeEnum.ERROR).message("添加员工失败，用户名重复！");
        }
        //2、对密码进行加密
        if (!"".equals(employee.getPassword())&&employee.getPassword()!=null){
            BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
            employee.setPassword(bc.encode(employee.getPassword()));
        }
        //2、修改用户信息
        int updateResult = employeeMapper.updateById(employee);
        if (updateResult==1){
            return R.success(AppHttpCodeEnum.SUCCESS).message("修改成功！");
        }
        return R.error(AppHttpCodeEnum.ERROR).message("修改失败，请联系管理员！");
    }

    /**
     * 根据修改用户密码
     * @param originalPassword 用户原始密码
     * @param password 用户新密码
     * @param request
     * @return
     */
    @Override
    public R updateEmployeePasswordById(String originalPassword, String password, HttpServletRequest request) {
        //1、非空判断
        if ("".equals(originalPassword)){
            return R.error(AppHttpCodeEnum.ERROR).message("密码修改失败，原密码为空！");
        }
        if ("".equals(password)){
            return R.error(AppHttpCodeEnum.ERROR).message("密码修改失败，新密码为空！");
        }
        //2、解析 token 获取员工 id
        Integer id = Integer.valueOf(String.valueOf(JwtUtils.getUserIdByJwtToken(request)));
        //3、根据用户 id，查询修改用户
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.eq(Employee::getId, id);
        Employee employee = employeeMapper.selectOne(qw);
        //4、密码校验
        BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
        //两个参数，加密前(即用户输入的明文密码) ，加密后(存放在数据库里的加密密码)
        boolean matches = bc.matches(originalPassword, employee.getPassword());
        if (!matches){
            return R.error(AppHttpCodeEnum.ERROR).message("原密码错误，请重新输入");
        }
        //5、将用户输入的新密码进行加密
        String newPassword = bc.encode(password);
        //6、将新密码写入数据库
        LambdaUpdateWrapper<Employee> uw = new LambdaUpdateWrapper<>();
        uw.eq(Employee::getId, id);
        uw.set(Employee::getPassword, newPassword);
        int update = employeeMapper.update(null, uw);
        if (update==1){
            return R.success(AppHttpCodeEnum.SUCCESS).message("密码修改成功");
        }
        return R.error(AppHttpCodeEnum.ERROR).message("密码修改失败，系统错误，请联系管理员！");
    }

    /**
     * 添加员工
     * @param employee
     * @return
     */
    @Override
    public R addEmployee(Employee employee) {
        //1、判断用户名是否重复
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.eq(Employee::getUsername, employee.getUsername());
        qw.last("limit 1");
        Employee selectResult = employeeMapper.selectOne(qw);
        if (!Objects.isNull(selectResult)){
            return R.error(AppHttpCodeEnum.ERROR).message("添加员工失败，用户名重复！");
        }
        //2、将明文密码加密后存入数据库
        BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
        employee.setPassword(bc.encode(employee.getPassword()));
        //3、设置默认头像
        employee.setImage(defaultImgUrl);
        //4、添加员工
        int insertResult = employeeMapper.insert(employee);
        if (insertResult==1){
            return R.success(AppHttpCodeEnum.SUCCESS).message("员工添加成功");
        }
        return R.error(AppHttpCodeEnum.ERROR).message("员工添加失败，系统错误，请联系管理员");
    }

    /**
     * 根据员工id，删除员工
     * 并且删除 员工的角色信息
     * @param id
     * @return
     */
    @Override
    public R deleteEmployeeById(Integer id) {
        if (id==1){
            return R.error(AppHttpCodeEnum.ERROR).message("无法删除超级管理员");
        }
        int deleteResult = employeeMapper.deleteById(id);
        // 删除员工角色信息
        employeeMapper.deleteRoleByEmployeeId(id);
        // 删除员工
        if (deleteResult==1){
            return R.success(AppHttpCodeEnum.SUCCESS).message("员工删除成功");
        }

        return R.error(AppHttpCodeEnum.ERROR).message("员工删除失败，系统错误，请联系管理员");
    }

    /**
     * 批量删除 超级管理员不能删除，跳过
     * 并且删除 员工的角色信息
     * @param ids 员工 id数组
     * @return
     */
    @Override
    public R deleteEmployeeBatch(List<Integer> ids) {
        //当数组只有一个id时，判断是不是超级管理员角色
        if (ids.size() == 1&&ids.get(0)==1){
            return R.error(AppHttpCodeEnum.ERROR).message("批量删除失败，无法删除超级管理员");
        }
        //当数组只有一个id时，超级管理员角色跳过
        for (Integer id: ids) {
            if (id == 1){
                continue;
            }
            int deleteResult = employeeMapper.deleteById(id);
            // 删除员工角色信息
            employeeMapper.deleteRoleByEmployeeId(id);
            if (deleteResult!=1){
                return R.error(AppHttpCodeEnum.ERROR).message("批量删除失败，系统错误，请联系管理员");
            }
        }
        return R.success(AppHttpCodeEnum.SUCCESS).message("批量删除成功");
    }

    /**
     * 根据员工id，查询单个员工id，密码返回为空，不显示
     * @param id
     * @return
     */
    @Override
    public R selectOneById(Integer id) {
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.eq(Employee::getId, id);
        Employee employee = employeeMapper.selectOne(qw);
        employee.setPassword("");
        return R.success(AppHttpCodeEnum.SUCCESS).message("根据id，查询单个员工数据").data("employee", employee);
    }

    /**
     * 员工信息 -- 导出 Excel
     * @param response
     */
    @Override
    public void outExcel(HttpServletResponse response) {
        try{
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            //1、 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("职工基本信息表", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            //2、查询数据库数据
            LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
            List<Employee> employees = baseMapper.selectList(qw);
            //查询职工角色信息 E asyExcel 不支持 List 数据可能会报错，需要自定义 Converter 实现类
            for (Employee employee: employees) {
                List<String> roleList = employeeMapper.selectByEmployeeId(employee.getId());
                employee.setRoleName(roleList);
            }

            //3、设置要排除的字段
            HashSet<String> set = new HashSet<>();
            set.add("password"); //密码不导出

            EasyExcel.write(response.getOutputStream(),Employee.class)
                    .sheet("员工基本信息表")
                    .excludeColumnFiledNames(set)
                    // 自定义 Converter 接口 实现类
                    .registerConverter(new ListStringConverter())
                    .doWrite(employees);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 导入 Excel
     * 导入操作Bug，因为密码使用了加密处理。我们在导出的 excel 文件里时候，密码是加密后的
     * 当我们 导入 Excel 时，每解析一行，调用添加方法，相当于对文明密码，做了二次加密，
     * 密码是单向加密的，无法进行破译后得到明文密码，再进行添加加密
     * 所以，不能做 员工操作 不能做导入操作
     * @param file
     */
//    @Override
//    public void inExcel(MultipartFile file) {
//        try {
//            EasyExcel.read(file.getInputStream(), Employee.class, new AnalysisEventListener<Employee>() {
//
//                @Override
//                public void doAfterAllAnalysed(AnalysisContext arg0) {
//                    System.out.println("Excel全部读完被执行......");
//                }
//
//                @Override
//                public void invoke(Employee data, AnalysisContext arg1) {
//                    System.out.println("解析一行: "+ data);
//                    addEmployee(data);
//                }
//            }).sheet("员工基本信息表").doRead();
//        }catch (Exception e){
//            e.printStackTrace();
//        }
//    }

    /**
     * 为 员工添加角色
     * @param id 待添加的员工id
     * @param roleIds 添加的角色id数组
     * @return
     */
    @Override
    public R addRoleForEmployee(Integer id, List<Integer> roleIds) {
        //1、添加前先删除 这个员工的所有 角色信息
        employeeMapper.deleteRoleByEmployeeId(id);
        //2、添加
        for (Integer roleId: roleIds) {
            employeeMapper.addRoleForEmployee(id, roleId);
        }
        return R.success(AppHttpCodeEnum.SUCCESS).message("员工角色分配成功！");
    }

    /**
     * 获取员工管理分页列表信息
     * @param pageCurrent 当前页码
     * @param pageSize 每页条数
     * @param name 查询关键字
     * @return
     */
    @Override
    public R getPage(Integer pageCurrent, Integer pageSize, String name) {
        Page<Employee> pageInfo = new Page<>(pageCurrent, pageSize);
        LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper<>();
        qw.like(name!=null, Employee::getUsername, name);
        Page<Employee> employeePage = employeeMapper.selectPage(pageInfo, qw);
        System.out.println("employeePage=" + employeePage);
        // 添加角色名字数据
        List<Employee> employees = employeePage.getRecords();
        for (Employee employee: employees) {
            List<String> roleList = employeeMapper.selectByEmployeeId(employee.getId());
            employee.setRoleName(roleList);
        }
        return R.success(AppHttpCodeEnum.SUCCESS).message("员工分页信息").data("employeePage", employeePage);
    }

    /**
     * 修改员工当前状态
     * @param id 要修改的用户id
     * @param status 状态码 0：正常 1：异常
     */
    @Override
    public void setStatus(Integer id, Integer status) {
        LambdaUpdateWrapper<Employee> uw = new LambdaUpdateWrapper<>();
        uw.eq(Employee::getId, id);
        uw.set(Employee::getStatus, status);
        employeeMapper.update(null, uw);
    }
}
